package com.we.wfc.file.service;

import cn.hutool.core.util.IdUtil;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.DefaultFastFileStorageClient;
import com.google.common.collect.Sets;
import com.google.gson.reflect.TypeToken;
import com.we.wfc.common.constants.BaseConstants;
import com.we.wfc.common.enums.ReturnCode;
import com.we.wfc.common.pojo.ResultPoJo;
import com.we.wfc.common.utils.ConverterUtil;
import com.we.wfc.common.utils.IOUtils;
import com.we.wfc.file.entity.Files;
import com.we.wfc.file.entity.WeFile;
import com.we.wfc.file.properties.WeFileProp;
import com.we.wfc.file.repository.WeFileRepo;
import com.we.wfc.file.vo.FileVo;
import com.we.wfc.file.vo.UploadVo;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

/**
 * @Description: FastDFS的服务层
 * @Author:Liangzy(Feeling)
 * @Date:Create in 2019/12/10 3:19 下午
 */
@Service
@Log4j2
@AllArgsConstructor
@EnableConfigurationProperties(WeFileProp.class)
public class FastDfsClientService {

    private final DefaultFastFileStorageClient defaultFastFileStorageClient;
    private final WeFileProp weFileProp;
    private final WeFileRepo weFileRepo;


    /**
     * 上传文件
     *
     * @param request
     * @param type         上传类型
     * @param sourNameFlag 是否显示源文件名
     * @param createBy     创建者
     * @return
     * @throws IOException
     */
    public ResultPoJo<UploadVo> uploadFile(MultipartHttpServletRequest request, String type, boolean sourNameFlag, String createBy) throws IOException {
        return uploadFile(request, type, sourNameFlag, null, createBy);
    }

    /**
     * 上传文件
     *
     * @param request
     * @param type         上传类型
     * @param sourNameFlag 是否显示源文件名
     * @param pattenRegex  自定义文件校验格式(正则表达式)
     * @param createBy     创建者
     * @return
     * @throws IOException
     */
    public ResultPoJo<UploadVo> uploadFile(MultipartHttpServletRequest request, String type, boolean sourNameFlag,
                                           String pattenRegex, String createBy) throws IOException {
        ResultPoJo<UploadVo> result = new ResultPoJo<>();
        UploadVo uploadVo = new UploadVo();
        boolean uploadFile = true;
        Iterator<String> itr = request.getFileNames();

        // 迭代每一个文件
        while (itr.hasNext()) {
            try {
                String paramName = itr.next();
                // 获取需要上传的文件
                List<MultipartFile> mfList = request.getFiles(paramName);
                for (MultipartFile mf : mfList) {
                    // 进行数据校验以及组装参数
                    uploadFile &= checkAndAssembly(result, uploadVo, paramName, mf, type, sourNameFlag, pattenRegex, createBy);
                    // 只要有一个文件校验没通过，就不做上传处理
                    if (!uploadFile) {
                        continue;
                    }
                }
            } catch (IOException e) {
                log.error("exception in uploadFile", e);
            }
        }
        result.setResult(uploadVo);
        return result;
    }

    /**
     * 数据校验以及组装参数
     *
     * @param result
     * @param uploadVo
     * @param paramName
     * @param mf
     * @param type
     * @param sourNameFlag
     * @param pattenRegex
     * @param createBy
     * @return
     * @throws IOException
     */
    public boolean checkAndAssembly(ResultPoJo<UploadVo> result, UploadVo uploadVo, String paramName, MultipartFile mf,
                                    String type, boolean sourNameFlag, String pattenRegex,
                                    String createBy) throws IOException {
        // 源数据集合
        Set<MetaData> MetaData = Sets.newHashSet();
        //返回Vo
        FileVo fileVo = new FileVo();
        //数据入库Entity
        WeFile weFile = new WeFile();

        //数据校验
        boolean checkResult = verifyFileinfo(result, mf, type, pattenRegex);
        //如果文件校验没有通过，则立即返回
        if (!checkResult) {
            return false;
        }
        // 上传者
        if (ConverterUtil.isNotEmpty(createBy)) {
            MetaData md = new MetaData("createBy", createBy);
            MetaData.add(md);
        }
        // 原文件名
        String oriFileName = mf.getOriginalFilename();
        String fileName = oriFileName;
        MetaData md = new MetaData("fileName", fileName);
        MetaData.add(md);
        // 上传到fastDFS并拿到返回路径
        String path = uploadFile(mf, MetaData);

        //新增到数据库中作为记录使用
        weFile.preInsert();
        weFile.setFileName(fileName);
        weFile.setFileUrl(path);
        //执行入库
        weFileRepo.save(weFile);

        // 入参返回对象
        fileVo.setPath(path);
        fileVo.setOriFileName(fileName);
        // 如果需要返回文件显示名就不转换path
        if (!sourNameFlag) {
            fileName = path.substring(path.lastIndexOf("/") + 1);
        }
        fileVo.setFileName(fileName);

        // 设置返回VO
        uploadVo.addFileVo(paramName, fileVo);
        // 如果配置了独立logback日志就存储 格式=文件名 原文件名 存储地址 创建者 创建时间
        log.info("We_File->{} {} {} {} {}",
                fileName, oriFileName, path, createBy,
                ConverterUtil.dateToString(new Date(), ConverterUtil.FORMATE_TIME_STAMP_24H_MLINE));
        return true;
    }

    /**
     * 上传文件
     *
     * @param file 文件对象
     * @return 文件访问地址
     * @throws IOException
     */
    public String uploadFile(MultipartFile file, Set<MetaData> metaData) throws IOException {
        // 是否配置了本地存储
        if (weFileProp.getLocalStorage().isEnable()) {
            String localFileAbsPath = getLocalStorageFileName(file.getOriginalFilename());
            File localFile = new File(localFileAbsPath);
            if (!localFile.getParentFile().exists()) {
                localFile.getParentFile().mkdirs();
            }
            // 生成缩略文件
            FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(localFile));
            // 保存源数据
            saveLocalMetaData(localFileAbsPath, metaData);
            // 返回相对目录
            return getLocalStorageViewPath(localFileAbsPath);
        } else {
            // fastDFS存储
            StorePath storePath = defaultFastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(),
                    FilenameUtils.getExtension(file.getOriginalFilename()), metaData);
            return getResAccessUrl(storePath);
        }
    }

    /**
     * 根据文件路径删除文件
     *
     * @param filePath
     */
    public ResultPoJo<ReturnCode> delFile(String filePath) {
        //返回对象模型
        ResultPoJo<ReturnCode> poJo = new ResultPoJo<>();

        defaultFastFileStorageClient.deleteFile(filePath);
        //逻辑删除数据库中的数据
        WeFile weFile = new WeFile();
        weFile.setFileUrl(filePath);
        Example<WeFile> of = Example.of(weFile);
        List<WeFile> all = weFileRepo.findAll(of);
        for (WeFile x : all) {
            x.setDelFlag((byte) 1);
        }
        weFileRepo.saveAll(all);

        poJo.setErrorStatus(ReturnCode.SUCCESS);
        return poJo;
    }

    /**
     * 封装图片完整URL地址
     *
     * @param storePath
     * @return
     */
    private String getResAccessUrl(StorePath storePath) {
        String fileUrl = storePath.getFullPath();
        return fileUrl;
    }

    /**
     * 校验文件信息
     * 描述:校验文件的大小是否小于等于配置文件中设置的阈值,以及校验文件的后缀是否是配置文件中定义的正则表达式
     */
    private boolean verifyFileinfo(ResultPoJo<UploadVo> result, MultipartFile file, String type, String pattenRegex) {
        // 得到文件后缀
        String fileExt = FilenameUtils.getExtension(file.getOriginalFilename());
        // 得到大小
        long size = file.getSize();
        // 声明大小验证结果
        int checkSize = 0;
        // 声明后缀验证结果
        Matcher matcher = null;
        // 声明整体验证结果
        boolean checkResults = true;

        // 流程分支
        if (BaseConstants.TYPE_IMAGE.equals(type)) {
            // 如果是图片需要比对大小和后缀
            checkSize = weFileProp.getImageMaxSize().compareTo(size);
            // 比对后缀
            matcher = weFileProp.getImagePatten().matcher(fileExt);
        } else if (BaseConstants.TYPE_FILE.equals(type)) {
            // 如果是文件需要比对大小和后缀
            checkSize = weFileProp.getFileMaxSize().compareTo(size);
            // 比对后缀
            matcher = weFileProp.getFilePatten().matcher(fileExt);
        } else if (BaseConstants.TYPE_VIDEO.equals(type)) {
            // 如果是文件需要比对大小和后缀
            checkSize = weFileProp.getVideoMaxSize().compareTo(size);
            // 比对后缀
            matcher = weFileProp.getVideoPatten().matcher(fileExt);
        }

        // 处理大小验证流程
        if (checkSize < 0) {
            checkResults = false;
            result.setErrorStatus(ReturnCode.FILE_SIZE_EXCEEDS_MAXIMUM);
            return checkResults;
        }

        // 处理后缀验证流程
        if (null != matcher && !matcher.find()) {
            checkResults = false;
            result.setErrorStatus(ReturnCode.FILE_TYPE_IS_NOT_ALLOWED);
            return checkResults;
        }

        return checkResults;
    }

    /**
     * @Author jirg
     * @Date 2019/3/4 15:00
     * @Description 下载网络文件到本地文件服务器并返回本地服务器地址
     **/
    public ResultPoJo<String> saveNetWorkFile(String fileNetWorkPath) {
        ResultPoJo<String> result = new ResultPoJo<>();
        try {
            UploadVo uploadVo = new UploadVo();
            // 源数据
            Set<MetaData> MetaData = Sets.newHashSet();
            // 上传者
            MetaData create = new MetaData("createBy", "1");
            MetaData.add(create);
            // 文件真实名称
            String fileName = fileNetWorkPath.substring(fileNetWorkPath.lastIndexOf("/"));
            MetaData md = new MetaData("fileName", fileName);
            MetaData.add(md);
            // 上传到fastDFS并拿到返回路径
            String path = "";
            // 获取网络文件流
            URL url = new URL(fileNetWorkPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            DataInputStream inputStream = new DataInputStream(conn.getInputStream());
            // 是否配置了本地存储
            if (weFileProp.getLocalStorage().isEnable()) {
                String localFileAbsPath = this.getLocalStorageFileName(fileName);
                File localFile = new File(localFileAbsPath);
                if (!localFile.getParentFile().exists()) {
                    localFile.getParentFile().mkdirs();
                }
                // 保存网络文件到本地
                FileUtils.copyInputStreamToFile(inputStream, localFile);
                // 保存源数据
                this.saveLocalMetaData(localFileAbsPath, MetaData);
                // 返回相对目录
                path = this.getLocalStorageViewPath(localFileAbsPath);
            } else {
                // fastDFS存储
                StorePath storePath = defaultFastFileStorageClient
                        .uploadFile(inputStream, conn.getContentLengthLong(),
                                FilenameUtils.getExtension(fileName), MetaData);
                path = storePath.getFullPath();
            }
            FileVo fileVo = new FileVo();
            fileVo.setPath(path);
            // 如果需要返回文件显示名就不转换path
            fileVo.setFileName(fileName);

            uploadVo.addFileVo(fileName, fileVo);

            // 写入数据库
            WeFile files = new WeFile(fileVo);
            files.preInsert();
            weFileRepo.save(files);

            result.setResult(path);
        } catch (IOException e) {
            log.error("exception in saveNetWorkFile", e);
        }
        return result;
    }

    /**
     * 保存本地MetaData
     *
     * @param masterFileAbsolutePath 主文件绝对路径
     * @param metaData               元数据
     */
    public void saveLocalMetaData(String masterFileAbsolutePath, Set<MetaData> metaData) {
        if (ConverterUtil.isNotEmpty(masterFileAbsolutePath)) {
            File masterFile = new File(masterFileAbsolutePath);
            // 文件必须存在 在保存MetaData之前要保证文件已经存储到本地
            if (!masterFile.exists()) {
                throw new RuntimeException("saveLocalMetaData File=" + masterFileAbsolutePath + " is not exists");
            }
            // 获取源数据文件
            File metaFile = getLocalMetaData(masterFile);
            String[] dataLines = {""};
            try {
                if (metaFile.exists()) {
                    // 文件存在就合并数据
                    dataLines = IOUtils.readLines(metaFile);
                    if (null != dataLines && dataLines.length > 0) {
                        Type classOfT = new TypeToken<Set<MetaData>>() {
                        }.getType();
                        // 读取已经存在的数据并转化成set
                        Set<MetaData> exisData = ConverterUtil.gson.fromJson(dataLines[0], classOfT);
                        // 合并set
                        mergeLocalMetaData(metaData, exisData);
                        // 赋值给文件行
                        dataLines[0] = ConverterUtil.gson.toJson(exisData);
                    }
                } else {
                    dataLines[0] = ConverterUtil.gson.toJson(metaData);
                }
                // 写入文件
                IOUtils.writeLines(metaFile, dataLines);
            } catch (IOException e) {
                log.error("error in saveLocalMetaData File = {}, MetaData = {}", masterFileAbsolutePath, dataLines);
            }
        }
    }

    public File getLocalMetaData(File masterFile) {
        String fileExtName = FilenameUtils.getExtension(masterFile.getName());
        return new File(masterFile.getAbsolutePath()
                .replace("." + fileExtName, Files.META_DATA_SUFFIX));
    }

    /**
     * 合并MetaData
     *
     * @param ori 源
     * @param tar 目标
     */
    public void mergeLocalMetaData(Set<MetaData> ori, Set<MetaData> tar) {
        Map<String, String> tarMap = tar.stream().collect(Collectors.toMap(MetaData::getName, MetaData::getValue));
        Map<String, String> oriMap = ori.stream().collect(Collectors.toMap(MetaData::getName, MetaData::getValue));
        tarMap.putAll(oriMap);
        tar.clear();
        tar.addAll(tarMap.entrySet().stream().map(k -> {
            return new MetaData(k.getKey(), k.getValue());
        }).collect(Collectors.toSet()));
    }

    /**
     * 获取本地存储文件新名称
     *
     * @param fileName
     * @return
     */
    public String getLocalStorageFileName(String fileName) {
        String extName = FilenameUtils.getExtension(fileName);
        OffsetDateTime now = OffsetDateTime.now();
        String newName = ConverterUtil.abs(ConverterUtil.toString(now.getYear()).hashCode()) +
                File.separator +
                ConverterUtil.toString(now.getDayOfYear()) +
                File.separator +
                ConverterUtil.getZeroFill(now.getHour(), 2) +
                File.separator +
                IdUtil.simpleUUID() + "." + extName;
        // 返回绝对目录
        String locPath = new File(weFileProp.getLocalStorage().getPath() +
                File.separator + newName).getAbsolutePath();
        return locPath;
    }

    /**
     * 获取返回路径
     *
     * @param absPath 绝对路径
     * @return
     */
    public String getLocalStorageViewPath(String absPath) {
        String locPath = new File(weFileProp.getLocalStorage().getPath()).getAbsolutePath();
        String filePath = new File(absPath).getAbsolutePath();
        return filePath.replace(locPath, "").replace("\\", "/").substring(1);
    }
}
